<!DOCTYPE HTML>
<html>

<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>优雅通过HttpClientFactory使用HttpClient | DeepThought</title>
  <meta name="google-site-verification" content="Ei6mmDdf-cJwox1RvJOeX8Dc720fqWjZQu-Ww2BjBt4">
  
  <meta name="author" content="积慕">
  
  <meta name="description" content=".net,dotnet,core,dotnet core,.netcore,.net core,模板,自定义,自定义模板,template,templateengine,template engine,core template,custom template,http,httpclient,client,client factory,http factory,httpclientfactory, ssl connection could not be established, ssl 报错, ssl 超时">
  
  
  <meta name="keywords" content=".net,dotnet,core,dotnet core,.netcore,.net core,模板,自定义,自定义模板,template,templateengine,template engine,core template,custom template,http,httpclient,client,client factory,http factory,httpclientfactory,httpclient ssl,.net core ssl, ssl connection timeout">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  
  <meta property="og:title" content="优雅通过HttpClientFactory使用HttpClient">
  <meta property="og:site_name" content="DeepThought">

  
  <meta property="og:image" content>
  

  <link href="https://pic.zhuliang.ltd/favicon.ico" rel="shortcut icon" type="image/x-icon">
  <link rel="alternate" href="/atom.xml" title="DeepThought" type="application/atom+xml">
  <link rel="stylesheet" href="/css/style.css" media="screen" type="text/css">

  <link rel="stylesheet" href="//at.alicdn.com/t/font_1187293_8fec2ou5fyx.css">

  <!-- <link rel="stylesheet" href="//cdn.bootcss.com/highlight.js/9.6.0/styles/github.min.css"> -->
  <!-- <script src="//cdn.bootcss.com/highlight.js/9.6.0/highlight.min.js"></script> -->
  <script src="/js/highlight.min.js"></script>

  <link rel="stylesheet" href="/css/markdown.css">

  <script src="/js/av-min.3.0.4.js"></script>
  <script src="/js/Valine.min.1.3.6.js"></script>

</head></html>

<body>
  <header id="header" class="inner"><div class="alignleft">
  <h1><a href="/">DeepThought</a></h1>
  <h2><a href="/">知识需沉淀，更需分享</a></h2>
</div>
<nav id="main-nav" class="alignright">
  <ul>
    
      <li><a href="/">首页</a></li>
    
      <li><a href="/about">关于我</a></li>
    
      <li><a href="/atom.xml">订阅RSS</a></li>
    
  </ul>
  <div class="clearfix"></div>
</nav>
<div class="clearfix"></div>
        </header>
  <div id="content" class="inner">
    <div id="main-col" class="alignleft"><div id="wrapper"><article class="post">
  
  <div class="post-content">
    <header>
      
        <div class="icon"></div>
      

 
      
  
    <h1 class="title">优雅通过HttpClientFactory使用HttpClient</h1>
  


      
        <time datetime="2019-02-17T16:27:53.000Z">
          create: 2019-02-18 00:27:53 | update: 2020-11-24 22:15:01
            <span style="float: right;">
    <span class="post-count" id="busuanzi_container_page_pv" style="display: inline;">
        
        本文总阅读量: <span id="busuanzi_value_page_pv"></span>&nbsp;次
        <span class="blueSeparate">&nbsp;|&nbsp;</span>
        
        <span class="post-count">文章总字数:&nbsp;2.2k&nbsp;字</span>
    <span class="blueSeparate">&nbsp;|&nbsp;</span>
    <span class="post-count">阅读约需:&nbsp;9&nbsp;分钟</span>
    <br>
</span>
</span>
        </time>
      
            
    </header>
    <div class="entry">
      
      <blockquote>
<p>本文主要讲解如何优雅的在 .NET CORE 中使用 HttpClient ，对比了下 Framework 和 Core 中的 Http 客户端。</p>
<p>提供了几个封装 HttpClient 的思路和经验。</p>
<p>看完此文，基本可以自行封装一个客户端</p>
</blockquote>
<h1 id="HttpClient-日常使用及坑点："><a href="#HttpClient-日常使用及坑点：" class="headerlink" title="HttpClient 日常使用及坑点："></a>HttpClient 日常使用及坑点：</h1><p>在C#中，平时我们在使用 HttpClient 的时候，会将 HttpClient 包裹在 using 内部进行声明和初始化，如：</p>
<a id="more"></a>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">using(var httpClient = new HttpClient())
{
    //other codes
}
</code></pre>
<p>至于为什么？无外乎是：项目代码中就是这样写的，依葫芦画瓢/别人就是这样用的/在微软官方的 ASP.NET 教程中也是这么干的。</p>
<p>说的技术范点：当你使用继承了 IDisposable 接口的对象时，建议在 using 代码块中声明和初始化，当 using 代码段执行完成后，会自动释放该对象而不需要手动进行显示 Dispose 操作。</p>
<p>但这里， HttpClient 这个对象有点特殊，虽然继承了 IDisposable 接口，但它是可以被共享的（或者说可以被复用），且线程安全。从项目经验来看，倒是建议在整个应用的生命周期内，复用 HttpClient 实例，而不是每次RPC请求的时候就实例化一个。（之前在优化公司一个web项目的时候，也曾经因为HttpClient载过一次坑，后面我会进行简述。）</p>
<p>我们先来用个简单的例子做下测试，看为什么不要每次RPC请求都实例化一个 HttpClient ：</p>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class Program
  {
      static void Main(string[] args)
      {
          HttpAsync();
          Console.WriteLine(&quot;Hello World!&quot;);
          Console.Read();
      }

      public static async void HttpAsync()
      {
          for (int i = 0; i &lt; 10; i++)
          {
              using (var client = new HttpClient())
              {
                  var result = await client.GetAsync(&quot;http://www.baidu.com&quot;);
                  Console.WriteLine($&quot;{i}:{result.StatusCode}&quot;);
              }
          }
      }
  }
</code></pre>
<p>运行项目输出结果后，通过 netstate 查看下 TCP 连接情况：</p>
<p><img src="https://pic.zhuliang.ltd/e92bde19-eae0-41a4-83ed-08c2038fdd0f.png-c" alt></p>
<ul>
<li>虽然项目已经运行结束，但是连接依然存在，状态为 “ TIME_WAIT” （继续等待看是否还有延迟的包会传输过来。）。<blockquote>
<p>默认在 windows 下， TIME_WAIT 状态将会使系统将会保持该连接 240s 。</p>
</blockquote>
</li>
</ul>
<ul>
<li>这里也就引出了我上面说的载过的一次坑：在高并发的情况下，连接来不及释放， socket 被耗尽，耗尽之后就会出现喜闻乐见的一个错误：</li>
</ul>
<p></p><p class="code-caption" data-lang="sh" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="sh">#使用 jemter 压测复现错误信息：
Unable to connect to the remote serverSystem.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.
</code></pre>
<p>说白话：就是会出现 “各种套接字问题” 。（码 WCF 的童鞋可能更加记忆尤新，问题追根溯源都是换汤不换药。）</p>
<p>熊厂里面能够搜索出来的解决方法，基本都是 “减少超时时间” ，但人为减少了超时时间会出现各种莫名其妙的错误。且无法避免服务器迟早崩溃的问题。</p>
<blockquote>
<p>可以通过注册表进行修改默认值：[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay]）</p>
</blockquote>
<p>那么如何处理这个问题？答案已经在上面说了， “复用HttpClient” 即可。如：</p>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class Program
{
    private static readonly HttpClient _client = new HttpClient();
    static void Main(string[] args)
    {
        HttpAsync();
        Console.WriteLine(&quot;Hello World!&quot;);
        Console.Read();
    }

    public static async void HttpAsync()
    {
        for (int i = 0; i &lt; 10; i++)
        {
            var result = await _client.GetAsync(&quot;http://www.baidu.com&quot;);
            Console.WriteLine($&quot;{i}:{result.StatusCode}&quot;);
        }
    }
}
</code></pre>
<p><img src="https://pic.zhuliang.ltd/232f7b9e-b9f3-4444-9697-e0108c3b09fe.png-c" alt></p>
<ul>
<li><p>可以看到，原先 10 个连接变成了 1 个连接。（请不要在意两次示例的目标 IP 不同—SLB导致的，都是百度的 ip）</p>
</li>
<li><p>另外，因为复用了 HttpClient ，每次 RPC 请求的时候，实际上还节约了创建通道的时间，在性能压测的时候也是很明显的提升。曾经因为这一举动，将 web项目 的 TPS 从单台 600 瞬间提升到了 2000+ ，页面请求时间也从 1-3s 减少至 100-300ms ，甚是让测试组小伙伴膜拜（当然也包括了一些业务代码的细调。），但知道个中缘由后，一个小改动带来的项目性能提升。。。会让人上瘾：）</p>
</li>
<li><p>至于如何创建一个静态 HttpClient 进行复用，大家可以按项目实际来，如直接创建一个 “全局” 静态对象，或者通过各类DI框架来创建均可。</p>
</li>
</ul>
<p>但这么调整 HttpClient 的引用后，依然存在一些问题可能会影响到你的项目（尚未影响到我:P），如：</p>
<ul>
<li>因为是复用的 HttpClient ，那么一些公共的设置就没办法灵活的调整了，如请求头的自定义。</li>
<li>因为 HttpClient 请求每个 url 时，会缓存该 url 对应的主机 ip ，从而会导致 DNS 更新失效（ TTL 失效了）</li>
</ul>
<p>那么有没有办法解决HttpClient的这些个问题？直到我遇到了 HttpClientFactory ，瞬间写代码幸福感倍升，也感慨新时代的童鞋们真的太幸福了，老一辈踩的坑可以 “完美” 规避掉了。</p>
<h2 id="HttpClientFactory优势："><a href="#HttpClientFactory优势：" class="headerlink" title="HttpClientFactory优势："></a>HttpClientFactory优势：</h2><blockquote>
<p>HttpClientFactory 是 ASP.NET CORE 2.1 中新增加的功能。</p>
</blockquote>
<ul>
<li>“完美”解决了我多年来遇到的这些坑，可以更加专注于业务代码。</li>
<li>HttpClientFacotry 很高效，可以最大程度上节省系统 socket 。（“JUST USE IT AND FXXK SHUT UP”:P）</li>
<li>Factory，顾名思义 HttpClientFactory 就是 HttpClient 的工厂，内部已经帮我们处理好了对 HttpClient 的管理，不需要我们人工进行对象释放，同时，支持自定义请求头，支持DNS更新等等等。<blockquote>
<p>从微软源码分析，HttpClient 继承自 HttpMessageInvoker ，而 HttpMessageInvoker 实质就是 HttpClientHandler 。</p>
<p>HttpClientFactory 创建的 HttpClient ，也即是 HttpClientHandler ，只是这些个 HttpClient 被放到了“池子”中，工厂每次在 create 的时候会自动判断是新建还是复用。(默认生命周期为 2 min)</p>
<p>还理解不了的话，可以参考 Task 和 Thread 的关系，以前碰到 HttpClient 这个问题的时候，就一直在想微软什么时候官方出一个 HttpClient 的 Factory ，虽然时隔了这么多年直到.NET CORE 2.1 才出，但也很是兴奋。</p>
</blockquote>
</li>
</ul>
<h1 id="HttpClientFactory使用方法："><a href="#HttpClientFactory使用方法：" class="headerlink" title="HttpClientFactory使用方法："></a>HttpClientFactory使用方法：</h1><blockquote>
<p>借助 ASP.NET CORE MVC ，可以很方便的进行 HttpClient 的使用</p>
</blockquote>
<ol>
<li><p>在 Startup.cs 中进行注册<br></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class Startup
{
   public Startup(IConfiguration configuration)
   {
       Configuration = configuration;
   }

   public IConfiguration Configuration { get; }

   // This method gets called by the runtime. Use this method to add services to the container.
   public void ConfigureServices(IServiceCollection services)
   {
       //other codes

       services.AddHttpClient(&quot;client_1&quot;,config=&gt;  //这里指定的 name=client_1 ，可以方便我们后期服用该实例
       {
           config.BaseAddress= new Uri(&quot;http://client_1.com&quot;);
           config.DefaultRequestHeaders.Add(&quot;header_1&quot;,&quot;header_1&quot;);
       });
       services.AddHttpClient(&quot;client_2&quot;,config=&gt;
       {
           config.BaseAddress= new Uri(&quot;http://client_2.com&quot;);
           config.DefaultRequestHeaders.Add(&quot;header_2&quot;,&quot;header_2&quot;);
       });
       services.AddHttpClient();

       //other codes
       services.AddMvc().AddFluentValidation();
   }
 }
</code></pre>
</li>
<li><p>使用，这里直接以 controller 为例，其他地方自行 DI<br></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class TestController : ControllerBase
{
 private readonly IHttpClientFactory _httpClient;
 public TestController(IHttpClientFactory httpClient)
 {
     _httpClient = httpClient;
 }

 public async Task&lt;ActionResult&gt; Test()
 {
     var client = _httpClient.CreateClient(&quot;client_1&quot;); //复用在 Startup 中定义的 client_1 的 httpclient
     var result = await client.GetStringAsync(&quot;/page1.html&quot;);

     var client2 = _httpClient.CreateClient(); //新建一个 HttpClient
     var result2 = await client.GetStringAsync(&quot;http://www.site.com/XXX.html&quot;);

     return null;
 }
}
</code></pre>
</li>
</ol>
<h2 id="实战用法2：使用自定义类执行-HttpClientFactory-请求"><a href="#实战用法2：使用自定义类执行-HttpClientFactory-请求" class="headerlink" title="实战用法2：使用自定义类执行 HttpClientFactory 请求"></a>实战用法2：使用自定义类执行 HttpClientFactory 请求</h2><ol>
<li>自定义 HttpClientFactory 请求类</li>
</ol>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class SampleClient
{
    public HttpClient Client { get; private set; }

    public SampleClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri(&quot;https://api.SampleClient.com/&quot;);
        httpClient.DefaultRequestHeaders.Add(&quot;Accept&quot;, &quot;application/json&quot;);
        httpClient.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, &quot;HttpClientFactory-Sample&quot;);
        Client = httpClient;
    }
}
</code></pre>
<ol start="2">
<li>在 Startup.cs 中 ConfigureService 方法中注册 SampleClient，代码如下，</li>
</ol>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">services.AddHttpClient&lt;SampleClient&gt;();
</code></pre>
<ol start="3">
<li><p>调用：<br></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class ValuesController : Controller
{
 private readonly SampleClient  _sampleClient;;

 public ValuesController(SampleClient  sampleClient)
 {
     _sampleClient = sampleClient;
 }

 [HttpGet]
 public async Task&lt;ActionResult&gt; Get()
 {
     string result = await  _sampleClient.client.GetStringAsync(&quot;/&quot;);
     return Ok(result);
 }
}
</code></pre>
<h2 id="实战用法3：完全封装-HttpClient-可以使用下面方法"><a href="#实战用法3：完全封装-HttpClient-可以使用下面方法" class="headerlink" title="实战用法3：完全封装 HttpClient 可以使用下面方法"></a>实战用法3：完全封装 HttpClient 可以使用下面方法</h2></li>
<li><p>自定义 HttpClientFactory 请求类</p>
</li>
</ol>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public interface ISampleClient
{
    Task&lt;string&gt; GetData();
}

public class SampleClient : ISampleClient
{
    private readonly HttpClient _client;

    public SampleClient(HttpClient httpClient)
    {
        httpClient.BaseAddress = new Uri(&quot;https://api.SampleClient.com/&quot;);
        httpClient.DefaultRequestHeaders.Add(&quot;Accept&quot;, &quot;application/json&quot;);
        httpClient.DefaultRequestHeaders.Add(&quot;User-Agent&quot;, &quot;HttpClientFactory-Sample&quot;);
        _client = httpClient;
    }

    public async Task&lt;string&gt; GetData()
    {
        return await _client.GetStringAsync(&quot;/&quot;);
    }
}
</code></pre>
<ol start="2">
<li>在Startup.cs中ConfigureService方法中注册SampleClient，代码如下，</li>
</ol>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">services.AddHttpClient&lt;ISampleClient, SampleClient&gt;();
</code></pre>
<ol start="3">
<li>调用：</li>
</ol>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">public class ValuesController : Controller
{
    private readonly ISampleClient  _sampleClient;;

    public ValuesController(ISampleClient  sampleClient)
    {
        _sampleClient = sampleClient;
    }

    [HttpGet]
    public async Task&lt;ActionResult&gt; Get()
    {
        string result = await _sampleClient.GetData();
        return Ok(result);
    }
}
</code></pre>
<h1 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h1><h2 id="SSL-connection-could-not-be-established"><a href="#SSL-connection-could-not-be-established" class="headerlink" title="SSL connection could not be established"></a>SSL connection could not be established</h2><p>解决方法：在注册 HttpClient 的时候，指定下 HttpClientHandler 忽略证书</p>
<p></p><p class="code-caption" data-lang="csharp" data-line_number="backend" data-trim_indent="backend" data-label_position="outer" data-labels_left="Code" data-labels_right=":" data-labels_copy="Copy Code"><span class="code-caption-label"></span></p><p></p>
<pre><code class="csharp">services.AddHttpClient&lt;IXXXClient, XXXClient&gt;().ConfigureHttpMessageHandlerBuilder(builder =&gt;
{
    builder.PrimaryHandler = new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =&gt; true
    };
});
</code></pre>
<p>另外需要注意下：你在本地调用的时候没有问题，但是一旦发布到服务器后，就各种 SSL 错误，请求超时等问题，此时你需要确认下请求的服务端是否有生成证书。</p>
<ul>
<li>比较好的情况是：通过 openssl 等证书生成工具，在服务器端生成 CA 文件，客户端自动下载这些 CA 文件，解决掉证书问题导致的 SSL 报错。</li>
<li>如果无法申请证书的，则建议还是走 http 协议的好，可以 http 和 https 都开启（但这里需要注意下，不要访问 http 的时候，全部跳转到 https 了）<ul>
<li>可以排查是否有在代码中使用指定 app.UseHttpsRedirection() </li>
<li>排查 IIS 中是否有设置 URL Rewrite</li>
<li>排查负载均衡中是否有相关选项</li>
</ul>
</li>
</ul>

      

      <div>
        
        <div style="background-color: rgb(255, 23, 0);user-select: auto;height: 3px;width:100%;margin-top: 40px;margin-bottom:15px;"></div>

<!-- <p>常言道：学然后知不足，教然后知困。</p>
<p>我知道你的焦虑，一起共进加油：P</p>
<p>关不关注都无所谓，会根据生活节奏紧凑度定期分享些开发经验、搬砖生涯、痛点、感悟。</p>
<p>
        <img src="https://pic.zhuliang.ltd/20191213133908.gif" alt="欢迎关注我的订阅号：P" />
</p> -->
<ul class="post-copyright" style="margin-left:0px;">
    <li class="post-copyright-author">
        <strong>本文作者：</strong>积慕
    </li>
    <li class="post-copyright-link">
        <strong>本文链接：</strong>
        <a href="/2019/02/backend/HttpClient-HttpClientFactory-InNetCore.html" title="优雅通过HttpClientFactory使用HttpClient">https://blog.zhuliang.ltd/2019/02/backend/HttpClient-HttpClientFactory-InNetCore.html</a>
    </li>
    <li class="post-copyright-license">
        <strong>版权： </strong>
        本站文章均采用 <a href="http://creativecommons.org/licenses/by-nc-sa/3.0/cn/" rel="external nofollow"
            target="_blank">CC BY-NC-SA 3.0 CN</a> 许可协议，请勿用于商业，转载注明出处！
    </li>
</ul>
        
      </div>
    </div>

    <footer>
      
      
  
  <div class="categories">
    <a href="/categories/backend/">backend</a>
  </div>

      
  
  <div class="tags">
    <a href="/tags/教程/">教程</a>, <a href="/tags/net-core/">.net core</a>
  </div>

      <!-- 
  <div class="addthis addthis_toolbox addthis_default_style">
    
    
      <a class="addthis_button_tweet"></a>
    
    
      <a class="addthis_button_google_plusone" g:plusone:size="medium"></a>
    
    
      <a class="addthis_button_pinterest_pinit" pi:pinit:layout="horizontal"></a>
    
    <a class="addthis_counter addthis_pill_style"></a>
  </div>
  <script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js"></script>
 -->
      
      <div class="clearfix"></div>
    </footer>
  </div>
</article>

<!-- 
<section id="comment">
  <h1 class="title">留言</h1>

  <div id="vcomments" style="background-color: #fff"></div>
  <script>
    new Valine({
      el: '#vcomments',
      appId: 'aTPKmG6tjDo3CbIXzAPzrj4X-gzGzoHsz',
      appKey: 'hNgWFneNpEcMnBJclPQnDgsd',
      notify: true,
      verify: true, //验证码
      avatar: 'monsterid', //https://valine.js.org/avatar.html
      placeholder: '请输入你的评论'
    })
  </script>
</section>
 --> <!--启用/关闭 LeanCloud 评论--></div></div>
    <aside id="sidebar" class="alignright">
  
<div class="widget tag">
  <h3 class="title"><i class="iconfont icon-category"></i>&nbsp;&nbsp;分类</h3>
  <ul class="entry">
  
    <li><a href="/categories/Obsidian/">Obsidian</a><small>1</small></li>
  
    <li><a href="/categories/PowerUser/">PowerUser</a><small>4</small></li>
  
    <li><a href="/categories/backend/">backend</a><small>57</small></li>
  
    <li><a href="/categories/frontend/">frontend</a><small>9</small></li>
  
    <li><a href="/categories/operations/">operations</a><small>5</small></li>
  
    <li><a href="/categories/提前退休/">提前退休</a><small>2</small></li>
  
  </ul>
</div>


  
<div class="widget tag">
  <h3 class="title"><i class="iconfont icon-new3"></i>&nbsp;&nbsp;最新文章</h3>
  <ul class="entry">
    
      <li>
        <a href="/2024/11/PowerUser/install-docker-in-pve-lxc.html">1. 通过 PVE LXC 搭建 docker 环境</a>
      </li>
    
      <li>
        <a href="/2024/11/Obsidian/obsidian-render-pie-chart.html">2. 2 套用于 obsidian 的生成时间占用饼图脚本示例，基于 templater， charts，echarts+dataview</a>
      </li>
    
      <li>
        <a href="/2024/10/PowerUser/fix-ios18-samba-readonly-issue.html">3. 解决 ios 18（包括ipadOS 18） 下通过文件(files app)连接 samba 服务器，目录变成只读权限的问题</a>
      </li>
    
      <li>
        <a href="/2023/07/operations/how-to-use-pm2.html">4. PM2 的安装、配置和使用</a>
      </li>
    
      <li>
        <a href="/2023/07/backend/expose-intranet-netcoreapi-to-internet.html">5. 通过 DDNS+显性URL 把内网部署的.NET CORE WEBAPI 项目暴露到公网中</a>
      </li>
    
  </ul>
</div>


  
<div class="widget tag">
  <h3 class="title"><i class="iconfont icon-tag"></i>&nbsp;&nbsp;标签</h3>
  <ul class="entry">
  
    <li><a href="/tags/net-core/">.net core</a><small>12</small></li>
  
    <li><a href="/tags/Debian/">Debian</a><small>2</small></li>
  
    <li><a href="/tags/Docker/">Docker</a><small>1</small></li>
  
    <li><a href="/tags/Elasticsearch/">Elasticsearch</a><small>35</small></li>
  
    <li><a href="/tags/Elasticsearch-Nest系列/">Elasticsearch.Nest系列</a><small>35</small></li>
  
    <li><a href="/tags/Obsidian/">Obsidian</a><small>1</small></li>
  
    <li><a href="/tags/PVE/">PVE</a><small>2</small></li>
  
    <li><a href="/tags/StackExchange-Redis系列/">StackExchange.Redis系列</a><small>12</small></li>
  
    <li><a href="/tags/centos/">centos</a><small>3</small></li>
  
    <li><a href="/tags/flex/">flex</a><small>1</small></li>
  
    <li><a href="/tags/ios/">ios</a><small>1</small></li>
  
    <li><a href="/tags/javascript/">javascript</a><small>1</small></li>
  
    <li><a href="/tags/linux/">linux</a><small>4</small></li>
  
    <li><a href="/tags/nginx/">nginx</a><small>2</small></li>
  
    <li><a href="/tags/nuget/">nuget</a><small>2</small></li>
  
    <li><a href="/tags/react/">react</a><small>6</small></li>
  
    <li><a href="/tags/styled-components/">styled-components</a><small>1</small></li>
  
    <li><a href="/tags/内网穿透/">内网穿透</a><small>4</small></li>
  
    <li><a href="/tags/培训讲义/">培训讲义</a><small>2</small></li>
  
    <li><a href="/tags/基础知识/">基础知识</a><small>2</small></li>
  
    <li><a href="/tags/性能优化/">性能优化</a><small>2</small></li>
  
    <li><a href="/tags/教程/">教程</a><small>64</small></li>
  
    <li><a href="/tags/理财/">理财</a><small>2</small></li>
  
    <li><a href="/tags/生命周期/">生命周期</a><small>1</small></li>
  
    <li><a href="/tags/经验/">经验</a><small>4</small></li>
  
  </ul>
</div>


  
  <script type="text/javascript" charset="utf-8" src="/js/tagcloud.js"></script>
  <script type="text/javascript" charset="utf-8" src="/js/tagcanvas.js"></script>
  <div class="widget tag">
    <h3 class="title"><i class="iconfont icon-cloud1"></i>&nbsp;&nbsp;标签云</h3>
    <div id="myCanvasContainer" class="widget tagcloud">
      <canvas width="250" height="250" id="resCanvas" style="width:100%">
        <a href="/tags/net-core/" style="font-size: 17.14px;">.net core</a> <a href="/tags/Debian/" style="font-size: 11.43px;">Debian</a> <a href="/tags/Docker/" style="font-size: 10px;">Docker</a> <a href="/tags/Elasticsearch/" style="font-size: 18.57px;">Elasticsearch</a> <a href="/tags/Elasticsearch-Nest系列/" style="font-size: 18.57px;">Elasticsearch.Nest系列</a> <a href="/tags/Obsidian/" style="font-size: 10px;">Obsidian</a> <a href="/tags/PVE/" style="font-size: 11.43px;">PVE</a> <a href="/tags/StackExchange-Redis系列/" style="font-size: 17.14px;">StackExchange.Redis系列</a> <a href="/tags/centos/" style="font-size: 12.86px;">centos</a> <a href="/tags/flex/" style="font-size: 10px;">flex</a> <a href="/tags/ios/" style="font-size: 10px;">ios</a> <a href="/tags/javascript/" style="font-size: 10px;">javascript</a> <a href="/tags/linux/" style="font-size: 14.29px;">linux</a> <a href="/tags/nginx/" style="font-size: 11.43px;">nginx</a> <a href="/tags/nuget/" style="font-size: 11.43px;">nuget</a> <a href="/tags/react/" style="font-size: 15.71px;">react</a> <a href="/tags/styled-components/" style="font-size: 10px;">styled-components</a> <a href="/tags/内网穿透/" style="font-size: 14.29px;">内网穿透</a> <a href="/tags/培训讲义/" style="font-size: 11.43px;">培训讲义</a> <a href="/tags/基础知识/" style="font-size: 11.43px;">基础知识</a> <a href="/tags/性能优化/" style="font-size: 11.43px;">性能优化</a> <a href="/tags/教程/" style="font-size: 20px;">教程</a> <a href="/tags/理财/" style="font-size: 11.43px;">理财</a> <a href="/tags/生命周期/" style="font-size: 10px;">生命周期</a> <a href="/tags/经验/" style="font-size: 14.29px;">经验</a>
      </canvas>
    </div>
  </div>




</aside>
    <div class="clearfix"></div>
  </div>
  <footer id="footer" class="inner"><div class="alignleft">

  
  &copy; 2018 - 2024 &nbsp;<a href='/about'>积慕</a>
  
  &nbsp;&nbsp;<a href='https://www.cnblogs.com/deepthought/'>我的博客园地址</a>
  &nbsp;&nbsp;finalclassic#outlook.com
</div>

<div class="clearfix"></div>
<div>
  本站主题基于：<a href='https://github.com/hexojs/hexo-theme-light' rel="external nofollow">hexo-theme-light</a>
</div>
<div id="busuanzi_container_site_pv">
  本站总访问量&nbsp;<span id="busuanzi_value_site_pv"></span>&nbsp;次
</div>
<div style="width:300px;margin:0 auto; padding:20px 0;">
  <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=31011702004805" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;">
    <img src="https://pic.zhuliang.ltd/20200102164648.png" style="float:left;"><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">沪公网安备 31011702004805号</p></a>
</div></footer>
  <script src="/js/jquery-2.0.3.min.js"></script>
<script src="/js/jquery.imagesloaded.min.js"></script>
<script src="/js/gallery.js"></script>




<link rel="stylesheet" href="/fancybox/jquery.fancybox.css" media="screen" type="text/css">
<script src="/fancybox/jquery.fancybox.pack.js"></script>
<script type="text/javascript">
(function($){
  $('.fancybox').fancybox();
})(jQuery);
</script>


<script type="text/javascript">
hljs.initHighlightingOnLoad();
</script>

<!-- <script src="/js/busuanzi.pure.mini.js"></script> -->
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<script>
    var _hmt = _hmt || [];
    (function () {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?ea793b9c8d6b31df666a6bc15038e246";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>
<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        }
        else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>
</body>
</html>
